﻿global using Devonline.Core;
global using Devonline.Entity;
using System.Globalization;
using System.Linq.Dynamic.Core;
using System.Reflection;
using System.Security.Claims;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using Devonline.Communication.Messages;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;

namespace Devonline.AspNetCore;

public static class AspNetCoreExtensions
{
    #region application builder extensions
    /// <summary>
    /// 使用 TSetting 类型获取配置文件 Application Setting 节点配置项, 并以单例形式添加到服务容器并返回
    /// </summary>
    /// <typeparam name="TSetting">Application Setting 配置项的类型名称</typeparam>
    /// <param name="builder">WebApplicationBuilder 实例</param>
    /// <param name="section">自定义设置节点名称, 默认配置节点: AppSetting</param>
    /// <param name="setup">在注册到依赖注入容器之前的处理方法</param>
    /// <returns></returns>
    public static TSetting AddSetting<TSetting>(this WebApplicationBuilder builder, string? section = default, Action<TSetting>? setup = default) where TSetting : class, new()
    {
        var setting = (string.IsNullOrWhiteSpace(section) || section == nameof(AppSetting)) ? builder.Configuration.GetSetting<TSetting>() : builder.Configuration.GetSetting<TSetting>(section) ?? new TSetting();
        setup?.Invoke(setting);
        builder.Services.AddSetting(setting);
        return setting;
    }
    /// <summary>
    /// 注册通讯器
    /// </summary>
    /// <param name="builder"></param>
    /// <param name="setup">在注册到依赖注入容器之前的处理方法</param>
    /// <returns></returns>
    /// <exception cref="Exception"></exception>
    public static IServiceCollection AddCommunicator(this WebApplicationBuilder builder, Action<MessageOptions>? setup = null)
    {
        var setting = builder.AddSetting<MessageOptions>(nameof(MessageOptions.Communicator));
        if (string.IsNullOrWhiteSpace(setting.Host) || string.IsNullOrWhiteSpace(setting.User))
        {
            throw new Exception("配置文件缺少必要的交互通讯配置");
        }

        setup?.Invoke(setting);
        return builder.Services.AddSingleton<IMessageCommunicator, MessageCommunicator>();
    }
    #endregion

    #region service extensions
    /// <summary>
    /// 注册默认的基于字符串主键的实体数据对象模型数据操作相关服务
    /// </summary>
    /// <typeparam name="TSetting">Application Setting 配置项的类型名称</typeparam>
    /// <param name="services">依赖注入容器服务</param>
    /// <param name="setting">Application Setting 配置项的实例</param>
    /// <returns></returns>
    public static IServiceCollection AddSetting<TSetting>(this IServiceCollection services, TSetting setting) where TSetting : class, new()
    {
        services.AddSingleton(setting);
        if (typeof(TSetting) != typeof(AppSetting) && setting is AppSetting appSetting)
        {
            services.AddSingleton(appSetting);
        }

        return services;
    }
    /// <summary>
    /// 注册默认的基于字符串主键的实体数据对象模型数据操作相关服务
    /// </summary>
    /// <param name="services"></param>
    /// <returns></returns>
    public static IServiceCollection AddDataService(this IServiceCollection services)
    {
        services.AddScoped(typeof(IDataService<,>), typeof(DataService<,>));
        services.AddScoped(typeof(IAttachmentService<>), typeof(AttachmentService<>));
        services.AddScoped(typeof(IDataWithAttachmentService<,>), typeof(DataWithAttachmentService<,>));
        services.AddScoped(typeof(IDataWithAdditionalService<,,>), typeof(DataWithAdditionalService<,,>));
        services.AddScoped(typeof(IDataWithCollectionService<,,>), typeof(DataWithCollectionService<,,>));
        services.AddScoped(typeof(IDataWithAttachmentAndAdditionalService<,,>), typeof(DataWithAttachmentAndAdditionalService<,,>));
        return services;
    }
    /// <summary>
    /// 注册默认的基于字符串主键的 Excel 导入导出操作相关服务
    /// </summary>
    /// <param name="services"></param>
    /// <returns></returns>
    public static IServiceCollection AddExcelService(this IServiceCollection services)
    {
        services.AddTransient<IExcelExportService, ExcelExportService>();
        services.AddTransient<IExcelImportService, ExcelImportService>();
        return services;
    }
    /// <summary>
    /// 注册通讯器
    /// </summary>
    /// <param name="services">依赖注入容器服务</param>
    /// <param name="options">Message Options 配置项的实例</param>
    /// <returns></returns>
    /// <exception cref="Exception"></exception>
    public static IServiceCollection AddCommunicator(this IServiceCollection services, MessageOptions options)
    {
        if (string.IsNullOrWhiteSpace(options.Host) || string.IsNullOrWhiteSpace(options.User))
        {
            throw new Exception("缺少必要的交互通讯配置");
        }

        return services.AddSetting(options).AddSingleton<IMessageCommunicator, MessageCommunicator>();
    }
    #endregion

    #region mvc & api extensions
    /// <summary>
    /// 用于依赖注入的 json 全局设置
    /// 为避免客户端字段名首字母大小写的问题, 将不再使用 CamelCase
    /// </summary>
    /// <param name="builder"></param>
    /// <returns></returns>
    public static IMvcBuilder AddJsonOptions(this IMvcBuilder builder, bool datetimeToString = false)
    {
        builder.AddJsonOptions(option =>
        {
            option.JsonSerializerOptions.AllowTrailingCommas = true;
            option.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
            option.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
            option.JsonSerializerOptions.IgnoreReadOnlyFields = false;
            option.JsonSerializerOptions.IgnoreReadOnlyProperties = false;
            option.JsonSerializerOptions.IncludeFields = false;
            option.JsonSerializerOptions.MaxDepth = AppSettings.UNIT_FOUR;
            option.JsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString | JsonNumberHandling.AllowNamedFloatingPointLiterals;
            option.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
            option.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
            option.JsonSerializerOptions.ReadCommentHandling = JsonCommentHandling.Skip;
            option.JsonSerializerOptions.WriteIndented = true;

            //枚举使用字符串表示形式
            option.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase));

            if (datetimeToString)
            {
                //自定义时间格式
                option.JsonSerializerOptions.Converters.Add(new DateTimeConverter(AppSettings.DEFAULT_DATETIME_FORMAT));
                option.JsonSerializerOptions.Converters.Add(new DateTimeOffsetConverter(AppSettings.DEFAULT_DATETIME_FORMAT));
            }
        });

        return builder.AddNewtonsoftJson(option =>
        {
            //使用驼峰样式的key
            option.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();

            //忽略循环引用
            option.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
            option.SerializerSettings.TypeNameHandling = TypeNameHandling.None;
            option.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;

            //设置时间格式
            option.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Local;
            option.SerializerSettings.DateParseHandling = DateParseHandling.DateTime;
            option.SerializerSettings.DateFormatHandling = DateFormatHandling.IsoDateFormat;

            if (datetimeToString)
            {
                //自定义时间格式
                option.SerializerSettings.DateFormatString = AppSettings.DEFAULT_DATETIME_FORMAT;
            }

            //枚举类型转字符串输出
            option.SerializerSettings.Converters.Add(new StringEnumConverter());
        });
    }
    #endregion

    #region http request extensions
    /// <summary>
    /// 获取查询选项, 将来自 HttpPost 的 OData 查询选项加入到 QueryString 键值对中
    /// </summary>
    /// <param name="request">WebApi 的 HttpRequest 对象</param>
    /// <returns>返回合并后的查询选项</returns>
    public static IEnumerable<KeyValuePair<string, StringValues>> GetRequestOptions(this HttpRequest request)
    {
        var queryString = request.Query.ToList();
        if (request.HasFormContentType && (request.Form.Keys?.Count ?? 0) > 0)
        {
            queryString.AddRange(request.Form);
        }

        if (request.Headers.IsNotNullOrEmpty())
        {
            queryString.AddRange(request.Headers);
        }

        if (request.Cookies.IsNotNullOrEmpty())
        {
            queryString.AddRange(request.Cookies.ToDictionary(x => x.Key, x => new StringValues(x.Value)));
        }

        return queryString;
    }
    /// <summary>
    /// 从当前 request 中获取参数的值, 取值顺序 Query -> Form -> Header -> Cookie
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="request"></param>
    /// <param name="key"></param>
    /// <returns></returns>
    public static T? GetRequestOption<T>(this HttpRequest request, string key) where T : IConvertible
    {
        var value = string.Empty;
        if (request.Query.Keys.Contains(key))
        {
            value = request.Query[key];
        }
        else if (request.HasFormContentType && request.Form.Keys.IsNotNullOrEmpty() && request.Form.Keys.Contains(key))
        {
            value = request.Form[key];
        }
        else if (request.Headers.IsNotNullOrEmpty() && request.Headers.ContainsKey(key))
        {
            value = request.Headers[key];
        }
        else if (request.Cookies.IsNotNullOrEmpty() && request.Cookies.Keys.Contains(key))
        {
            value = request.Cookies[key];
        }

        if (!string.IsNullOrWhiteSpace(value))
        {
            return (T)Convert.ChangeType(value, typeof(T), CultureInfo.CurrentCulture);
        }

        return default;
    }
    /// <summary>
    /// 获取请求的 origin 字符串
    /// </summary>
    /// <param name="request"></param>
    /// <returns></returns>
    public static string GetRequestOrigin(this HttpRequest request) => new(new StringBuilder()
         .Append(request.Scheme)
         .Append(AppSettings.DEFAULT_PROTOCOL_SPLITER)
         .Append(request.Host)
         .ToString());
    /// <summary>
    /// 获取请求的 path 字符串, 不带请求参数的部分
    /// </summary>
    /// <param name="request"></param>
    /// <returns></returns>
    public static string GetRequestPath(this HttpRequest request)
    {
        var builder = new StringBuilder()
             .Append(request.Scheme)
             .Append(AppSettings.DEFAULT_PROTOCOL_SPLITER)
             .Append(request.Host)
             .Append(request.PathBase);
        if (request.Path.HasValue && request.Path != AppSettings.CHAR_SLASH.ToString())
        {
            builder.Append(request.Path);
        }

        return new(builder.ToString());
    }
    /// <summary>
    /// 获取请求的完整 uri 字符串
    /// </summary>
    /// <param name="request"></param>
    /// <returns></returns>
    public static string GetRequestUri(this HttpRequest request)
    {
        var builder = new StringBuilder()
             .Append(request.Scheme)
             .Append(AppSettings.DEFAULT_PROTOCOL_SPLITER)
             .Append(request.Host)
             .Append(request.PathBase);
        if (request.Path.HasValue && request.Path != AppSettings.CHAR_SLASH.ToString())
        {
            builder.Append(request.Path).Append(request.QueryString);
        }

        return new(builder.ToString());
    }
    /// <summary>
    /// 获取请求中的 columns 字段列表并转换为字符串数组
    /// </summary>
    /// <typeparam name="T">字段存在的校验类型</typeparam>
    /// <param name="request">Http 请求</param
    /// <param name="useCamelCase">是否使用驼峰法</param>
    /// <returns></returns>
    public static IEnumerable<string>? GetRequestColumns<T>(this HttpRequest request, bool useCamelCase = true)
    {
        string? columns = request.GetRequestOption<string>(nameof(columns));
        if (columns is not null)
        {
            var propertyInfos = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
            var columnNames = columns.Split(AppSettings.CHAR_COMMA, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
            if (columnNames is not null && columnNames.Length > 0)
            {
                if (useCamelCase)
                {
                    columnNames = columnNames.Select(x => x.FirstCharToUpper()).ToArray();
                }

                var notExists = columnNames.Where(x => !propertyInfos.Any(a => a.PropertyType.GetCoreType().IsSampleType() && a.Name == x));
                if (notExists.Any())
                {
                    throw new HttpRequestException($"请求参数 columns 中, 列 {notExists.ToString<string>()} 不存在!");
                }

                return columnNames;
            }
        }

        return null;
    }
    /// <summary>
    /// 通过 request query 获取 QueryOptions 的实例
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="request"></param>
    /// <returns></returns>
    public static QueryOptions<T> GetQueryOptions<T>(this HttpRequest request) => new(new QueryOptionRawValue(request.Query));
    /// <summary>
    /// http request 基础分页请求参数
    /// </summary>
    /// <param name="request"></param>
    /// <returns></returns>
    public static PagedRequest GetPagedRequest(this HttpRequest request)
    {
        var pagedRequest = new PagedRequest
        {
            PageIndex = request.GetRequestOption<int>(nameof(PagedRequest.PageIndex).FirstCharToLower()),
            PageSize = request.GetRequestOption<int>(nameof(PagedRequest.PageSize).FirstCharToLower()),
            OrderBy = request.GetRequestOption<string>(nameof(PagedRequest.OrderBy).FirstCharToLower()) ?? request.GetRequestOption<string>(nameof(PagedRequest.OrderBy).ToLowerInvariant())
        };

        if (pagedRequest.PageIndex <= AppSettings.UNIT_ONE)
        {
            pagedRequest.PageIndex = request.GetRequestOption<int>(nameof(PagedRequest.PageIndex).ToLowerInvariant());
            if (pagedRequest.PageIndex <= AppSettings.UNIT_ONE)
            {
                pagedRequest.PageIndex = AppSettings.DEFAULT_PAGE_INDEX;
            }
        }

        if (pagedRequest.PageSize <= AppSettings.UNIT_ONE)
        {
            pagedRequest.PageSize = request.GetRequestOption<int>(nameof(PagedRequest.PageSize).ToLowerInvariant());
            if (pagedRequest.PageSize <= AppSettings.UNIT_ONE)
            {
                pagedRequest.PageSize = AppSettings.DEFAULT_PAGE_SIZE;
            }
        }

        return pagedRequest;
    }
    /// <summary>
    /// 根据上下文和请求中的分页条件获取分页的查询结果
    /// </summary>
    /// <typeparam name="T">操作的数据类型</typeparam>
    /// <param name="request">Http 请求</param>
    /// <param name="queryable">针对数据的查询表达式</param>
    /// <returns></returns>
    public static async Task<PagedResult<T>> GetPagedResultAsync<T>(this HttpRequest request, IQueryable<T> queryable) => await queryable.PageByAsync(request.GetPagedRequest());
    #endregion

    #region http context extensions
    /// <summary>
    /// 获取上下文对象中指定 Claim type 的值
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="httpContext"></param>
    /// <param name="type"></param>
    /// <returns></returns>
    public static T? GetClaimValue<T>(this HttpContext httpContext, string type) where T : IConvertible
    {
        var value = httpContext.User.Claims.FirstOrDefault(c => c.Type == type)?.Value;
        return !string.IsNullOrWhiteSpace(value) ? value.To<T>() : default;
    }
    /// <summary>
    /// 获取上下文对象中参数/变量的值, 先取 http context 的 Claims, 后取 request
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="httpContext"></param>
    /// <param name="key"></param>
    /// <returns></returns>
    public static T? GetContextOption<T>(this HttpContext httpContext, string key) where T : IConvertible => httpContext.GetClaimValue<T>(key) ?? httpContext.Request.GetRequestOption<T>(key);
    /// <summary>
    /// 从 httpContext 获取用户标识, 用户标识取值顺序为: 在 User.Claims 中的 type 为 sub; 在 request 中为 userId
    /// User.Claims 中的 type 为 userId 的值 -> User.Claims 中的 type 为 id 的值 -> User.Claims 中的 type 为 sub 的值
    /// 不在主动获取 request 中 userId 的参数值
    /// </summary>
    /// <param name="httpContext"></param>
    /// <returns></returns>
    public static string? GetUserId(this HttpContext httpContext) => httpContext.GetClaimValue<string>(AppSettings.CLAIM_TYPE_USER_ID)
            ?? httpContext.GetClaimValue<string>(AppSettings.CLAIM_TYPE_JWT_SUBJECT)
            ?? httpContext.GetClaimValue<string>(AppSettings.CLAIM_TYPE_ID)
            ?? httpContext.GetClaimValue<string>(ClaimTypes.NameIdentifier);
    /// <summary>
    /// 从 httpContext 获取用户名, 用户名取值顺序为:
    /// User.Claims 中的 type 为 userName 的值 -> User.Identity.Name -> User.Claims 中的 type 为 name, Name 的值, 无值则取 anonymous
    /// 不在主动获取 request 中 userName 的参数值
    /// </summary>
    /// <param name="httpContext"></param>
    /// <returns></returns>
    public static string GetUserName(this HttpContext httpContext) => httpContext.GetClaimValue<string>(AppSettings.CLAIM_TYPE_USER_NAME)
            ?? httpContext.User?.Identity?.Name
            ?? httpContext.GetClaimValue<string>(AppSettings.CLAIM_TYPE_NAME)
            ?? httpContext.GetClaimValue<string>(nameof(httpContext.User.Identity.Name))
            ?? AppSettings.USER_ANONYMOUS;
    #endregion

    /// <summary>
    /// 枚举按顺序生成基础数据
    /// </summary>
    /// <typeparam name="TEnum"></typeparam>
    /// <param name="index">当前枚举的分配编号</param>
    /// <param name="toLower">key 的值是否使用整体小写</param>
    /// <returns></returns>
    public static List<Parameter> GetParametersByEnum<TEnum>(this int index, bool toLower = false) where TEnum : struct, Enum
    {
        var type = typeof(TEnum);
        var parameter = new Parameter
        {
            Id = index.ToString(),
            Index = index,
            ParentId = "0",
            Key = toLower ? type.Name.ToLowerInvariant() : type.Name.FirstCharToLower(),
            Value = type.Name,
            Text = type.GetDisplayName()
        };

        index = 0;
        parameter.Create();
        var list = new List<Parameter> { parameter };
        var values = Enum.GetValues<TEnum>();
        foreach (var @enum in values)
        {
            var enumValue = Convert.ToInt32(@enum);
            var id = parameter.Index * AppSettings.UNIT_THOUSAND + enumValue;
            if (enumValue == index++)
            {
                id++;
            }

            var value = @enum.ToString();
            var child = new Parameter
            {
                Id = id.ToString(),
                Index = id,
                ParentId = parameter.Id,
                Key = toLower ? value.ToLowerInvariant() : value.FirstCharToLower(),
                Value = value,
                Text = @enum.GetDisplayName()
            };

            child.Create();
            list.Add(child);
        }

        return list;
    }

    /// <summary>
    /// 根据总数目, 页大小, 获取总页数
    /// </summary>
    /// <param name="total"></param>
    /// <param name="size"></param>
    /// <returns></returns>
    public static long GetPageCount(this long total, int size) => ((total - 1) / size) + 1;

    /// <summary>
    /// 输出包含进度百分比的日志
    /// </summary>
    /// <param name="logger"></param>
    /// <param name="index"></param>
    /// <param name="total"></param>
    public static void LogPercentage(this ILogger logger, string message, int index, long total)
    {
        var page = total.GetPageCount(AppSettings.UNIT_HUNDRED);
        var percent = Math.Round(index * 100 / (float)total);
        if (index % page == 0 || index == total)
        {
            if (logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Information))
            {
                int currentLineCursor = Console.CursorTop;
                Console.SetCursorPosition(AppSettings.UNIT_ZERO, currentLineCursor);
                Console.Write(new string(AppSettings.CHAR_SPACE, Console.WindowWidth));
                Console.SetCursorPosition(AppSettings.UNIT_ZERO, currentLineCursor);
            }

            logger.LogInformation($"{message}: the {index} of {total}, percent: {percent}%");
        }
        else
        {
            if (logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug))
            {
                int currentLineCursor = Console.CursorTop;
                Console.SetCursorPosition(AppSettings.UNIT_ZERO, currentLineCursor);
                Console.Write(new string(AppSettings.CHAR_SPACE, Console.WindowWidth));
                Console.SetCursorPosition(AppSettings.UNIT_ZERO, currentLineCursor);
            }

            logger.LogDebug($"{message}: the {index} of {total}, percent: {percent}%");
        }
    }

    /// <summary>
    /// 全局默认异常处理方法
    /// 在 WebApplication 下会自动记录异常日志
    /// </summary>
    /// <param name="app">IApplicationBuilder</param>
    /// <param name="action">Exception Handler</param>
    /// <returns></returns>
    public static IApplicationBuilder UseException(this IApplicationBuilder app, Action<HttpContext, Exception?>? action = default) => app.UseExceptionHandler(new ExceptionHandlerOptions
    {
        ExceptionHandler = async context =>
        {
            var message = "服务器内部错误";
            var logger = (app as WebApplication)?.Logger;
            var exception = context.Features.Get<IExceptionHandlerFeature>();
            if (exception != null && exception.Error != null)
            {
                var errorMessage = exception.Error.GetMessage();
                switch (exception.Error)
                {
                    case BadHttpRequestException:
                        //业务类型的错误需要抛出 BadHttpRequestException 异常, 一律按 BadRequest(400) 处理
                        context.Response.StatusCode = StatusCodes.Status400BadRequest;
                        message = exception.Error.Message;
                        logger?.LogInformation(message);
                        break;
                    case DbUpdateException:
                        //数据存在依赖不可删除时
                        context.Response.StatusCode = StatusCodes.Status400BadRequest;
                        message = "当前数据正在使用中, 不能删除, 请先删除正在使用此数据的依赖数据, 在尝试删除当前数据!";
                        logger?.LogWarning(message);
                        break;
                    default:
                        logger?.LogError(exception.Error, exception.Error.GetMessage());
                        break;
                }
#if DEBUG
                message = errorMessage;
#endif
            }

            //context.Response.ContentType = ContentType.Json;
            action?.Invoke(context, exception?.Error);
            await context.Response.WriteAsync(message).ConfigureAwait(false);
        }
    });
}